天啊,右邊 iT邦幫忙鐵人賽 怎麼每天的話都這麼勵志XD
「有些事現在不做 一輩子都不會做了 完成今天的鐵人文吧!」
始めましょう!!!!!!(讓我們開始吧!)
來了來了,隱挑戰重頭戲來了,
也是我一直很害怕面對的XD
讓我們試著思考一下,
一個回合:李逍遙選擇動作→趙靈兒選擇動作→兩位主角執行動作→怪動作
所以我們要把兩位主角所選擇的動作儲存下來。
然後這次隱挑戰為了降低難度,
只有攻擊沒有補血,
(所以我把李逍遙的氣療術跟趙靈兒的觀音咒拔掉了XD)
選擇攻擊後,要知道這次攻擊的量有多少,
然後怪也只有一隻,所以不會讓玩家選擇攻擊對象。(這也是為了降低難度XD)
總結一下,
所以要有一個物件陣列,
儲存各個角色這次選擇的動作,如果是選擇招式,還要儲存這次選擇的招式。
先很簡單的讓回合制可以運作吧!
這邊先寫一個角色的動作函數,
裡面都先做鍵盤事件監聽。
function roleActionSelect(){
console.log(`${roleStatus[currentActor].Name} 的回合!`);
warMenuElement.setAttribute("data-option","attack"); // 每次選項初始值為攻擊
switch(currentActor){
case 0: // 李逍遙
window.addEventListener("keydown", optionSelect);
break;
case 1: // 趙靈兒
window.addEventListener("keydown", optionSelect);
break;
case 2: // 草叢怪
window.addEventListener("keydown", optionSelect);
break;
default:
console.log("default");
break;
}
}
再來,在原本 optionSelect 的函數中多加一個 Enter 鍵的處理,
function optionSelect(event){
switch(event.keyCode){
... 前略 ...
case 13: // Enter 鍵
console.log(`現在的選項是:${warMenuElement.dataset.option}`);
window.removeEventListener("keydown", optionSelect); // 當按下 Enter 就是塵埃落定,不能再選擇選項
if ( currentActor === 2 ){ // 0→1→2 所以當2動作完畢要回到0
currentActor = 0;
} else{
currentActor += 1;
}
if ( roleStatus[2].HealthPoint[0] >0 ){ // 如果怪的血量 > 0 則要繼續戰鬥
roleActionSelect();
}
break;
}
... 後略 ...
}
再來,為了要讓戰鬥可以開始,
因此前面要加上初始值。
let currentActor = 0; // 0:李逍遙 1:趙靈兒 2:怪 與roleStatus順序一致
roleActionSelect(); // 戰鬥開始
我們要先看一次順序是不是能夠如我們想像的執行,
還有是不是怪的血量沒有歸0,
戰鬥就一直能夠持續,
來看一下 console.log 怎麼顯示的吧!
看起來有照我們所設定的執行,
不過這邊偷說,
其實我剛在寫這段的時候腦袋有點打結,
改了好幾次才改到現在這個樣子orz
再來就是要把角色所選擇的動作存下來了,
然後這邊想了一下,
選擇的動作既然是綁在角色上,
那何不把動作記在 roleStatus 裡就好呢?
That's right!!!!!
let roleStatus = [
{ Name: "李逍遙",
HealthPoint: [194,194], // HP(血量): 現在剩餘/總量
MagicPoint: [129,129], // MP(法力): 現在剩餘/總量
AttackPower: 25, // 攻擊力,決定普攻跟法攻的攻擊底量
DefensePower: 20, // 防禦力,決定被攻擊後會損多少血
Moves: [ // 角色會的招式
{ MoveName: "御劍術",
Cost: 10, // 招式所消耗法力
},
],
Action: {
OptionSelection: "", // 選擇的選項
SkillSelection: "", // 如果選擇的是招式,則要儲存選擇的招式
},
},
... 中略 ...
];
所以在 roleStatus 裡面多了一個 Action 的物件,
裡面放 OptionSelection 跟 SkillSelection 的屬性。
然後在 optionSelect 的函數裡,
按下 Enter 鍵的條件中,
儲存各角色 OptionSelection。
function optionSelect(event){
switch(event.keyCode){
... 中略 ...
case 13: // Enter 鍵
// console.log(`現在的選項是:${warMenuElement.dataset.option}`);
window.removeEventListener("keydown", optionSelect); // 當按下 Enter 就是塵埃落定,不能再選擇選項
roleStatus[currentActor].Action.OptionSelection = warMenuElement.dataset.option;
console.log(`${roleStatus[currentActor].Name} 選擇的動作是 ${roleStatus[currentActor].Action.OptionSelection}`);
... 後略 ...
}
一樣看看呈現結果:
再來就是要加上兩位主角選擇攻擊後執行扣血的機制了,
然後剛寫了一下稍微推翻了一些之前寫過的東西,
這邊重新順一下邏輯,
角色順序是:0→1→2
當輪到2(怪),就要開始執行動作(扣血),
且不需要有鍵盤事件監聽。
所以 roleActionSelect 改成這樣:
function roleActionSelect(){
console.log(`${roleStatus[currentActor].Name} 的回合!`);
warMenuElement.setAttribute("data-option","attack"); // 每次選項初始值為攻擊
switch(currentActor){
case 0: // 李逍遙
window.addEventListener("keydown", optionSelect);
break;
case 1: // 趙靈兒
window.addEventListener("keydown", optionSelect);
break;
case 2: // 草叢怪
roleActionExecute();
break;
default:
console.log("default");
break;
}
}
執行動作的函數 roleActionExecute 分成主角跟怪,
先主角執行動作,再來怪執行動作。
(以下都只先考慮怪會掛掉的情況,主角掛掉的情況先不考慮)
function roleActionExecute(){
// 主角執行動作
for ( let i=0; i<2; i++ ){
switch(roleStatus[i].Action.OptionSelection){
case "attack": // 動作為攻擊
let attackQty = roleStatus[i].AttackPower - roleStatus[2].DefensePower;
roleStatus[2].HealthPoint[0] -= attackQty;
console.log(`現在 ${roleStatus[2].Name} 的血量為 ${roleStatus[2].HealthPoint[0]}`);
break;
default:
console.log("default");
break;
}
// 如果怪的血量 <= 0,表示怪被消滅了,後續動作不用執行
if ( roleStatus[2].HealthPoint[0] <=0 ){
console.log("戰鬥勝利");
break;
}
}
// 如果怪的血量 > 0 才能執行動作
if ( roleStatus[2].HealthPoint[0] >0 ){
// 怪執行動作
if ( currentActor === 2 ){
let attackQty = roleStatus[currentActor].AttackPower - roleStatus[1].DefensePower;
roleStatus[1].HealthPoint[0] -= attackQty;
console.log(`現在 ${roleStatus[0].Name} 的血量為 ${roleStatus[0].HealthPoint[0]}`);
console.log(`現在 ${roleStatus[1].Name} 的血量為 ${roleStatus[1].HealthPoint[0]}`);
}
// 怪執行動作後,要再將做動的角色設回0,並再執行roleActionSelect
currentActor = 0;
roleActionSelect();
}
}
這邊看一下 console.log 吧!
看起來 OK 了!
那這邊把 1)扣血的顯示加到血條上面 2)怪的血條會跟著實際剩血量 試試看!
這邊我一樣習慣先在 html 寫死看看,
CSS 樣式調好了再改成 JavaScript。
html:
<div class="monster">
<div class="bloodMinus">-20</div>
<div class="blood"></div>
</div>
這很顯然不是我們要的樣子,
動手調整吧!
還有這邊我想加上閃爍樣式,
就會用到 Animate.css 的東西,
Animate.css 有各種特效的 CSS,
讓你使用時不需要自己弄 CSS 弄的很辛苦,
只要直接引用就好。
網站有使用教學,
總之就是在 html 的 head 裡面加上 CDN (CSS),
<head>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css">
<link rel="stylesheet" href="css/style.css">
</head>
再來就是在要使用的標籤加上規定的 CSS 名稱即可,
例如:
<div class="bloodMinus animate__animated animate__flash">-20</div>
再來我想要讓它閃爍完就消失,
這邊就讓我們移到 JavaScript 上控制吧!
-------------- N分鐘以後 --------------
好,我越寫越多問題了XD
就是因為我將李逍遙跟趙靈兒執行動作寫在同一個函數裡面,
雖然有用 for 迴圈決定執行順序,
但是程式執行太快,
所以最後都只會顯示趙靈兒攻擊的扣血顯示。
這邊我先求有再求好,
這樣李逍遙、趙靈兒、怪的執行動作也都必須用函數各別包起來,
再一一執行,
這樣才會有執行的先後順序。
函數:boyActionExecute, girlctionExecute, monsterActionExecute
然後我的寫法大概是:
function boyActionExecute(){
girlctionExecute();
}
function girlctionExecute(){
monsterActionExecute();
}
這邊我先提我延遲跟血條顯示的做法就好orz
延遲我是用 Lodash debounce,
像這樣:
let bloodMinusDebounce = _.debounce(function(){
monsterBloodMinusElement.textContent = "";
monsterBloodMinusElement.setAttribute("class", originalClass);
if ( roleStatus[2].HealthPoint[0] <=0 ){
console.log("戰鬥勝利");
} else{
girlctionExecute();
}
},1500);
bloodMinusDebounce();
然後血條的顯示在 JavaScript 是先算出 (現在血量/總血量)x100 再用 物件.style 設定寬度:
// 怪的血條寬度設定
let monsterBloodWidth = (roleStatus[2].HealthPoint[0]/roleStatus[2].HealthPoint[1])*100;
monsterBloodElement.style = `width: ${monsterBloodWidth}%`;
而要讓血條有慢慢變化的感覺,則是用 CSS transition。
要在血條的 CSS 加上:
.blood{
... 前略 ...
transition: all 0.8s;
}
這裡有大大兩年前鐵人賽的文章分享→ Day27:小事之 Transition 與 Animation
今日進度:
我今天的 code 真的亂到爆炸,
等一下或明天要來順一下才行orz
難怪我這次這麼害怕碰回合制,
因為我記得我去年也是搞很久orz
最後也是亂寫硬弄出來orz
明天先整理一下 code,
然後心有餘力就開始弄招式選擇吧QQ